package com.magicalcoder.pigeon.mysql.manager;

import com.magicalcoder.pigeon.mysql.manager.dto.JdbcProperties;
import com.magicalcoder.pigeon.mysql.manager.keepalive.SelectOne;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by hzhedongyu on 2017/6/6.
 */
public class PoolImpl implements Pool {

    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    private JdbcProperties jdbcProperties;

    private volatile List<PigeonConnection> busingQueue = new ArrayList<PigeonConnection>();//繁忙队列
    private volatile List<PigeonConnection> freeQueue = new ArrayList<PigeonConnection>();//空闲队列

    private SelectOne selectOne;

    public PoolImpl(JdbcProperties jdbcProperties) {
        this.jdbcProperties = jdbcProperties;
        init();
        demonGc();
    }

    private boolean canGc(){
        return busingQueue.size()+freeQueue.size()>jdbcProperties.getCoreSize();
    }

    //回收 考虑使用select 1策略 具体时间由客户端自行定义 目的就是避开wait_timeout
    private void demonGc(){
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for(;;){
                    try {
                        lock.lock();
                        if(canGc()){
                            if(freeQueue.size()>0){//满足回收条件
                                long now = System.currentTimeMillis();
                                for(int i=0;i<freeQueue.size();i++){
                                    PigeonConnection p = freeQueue.get(i);
                                    if(p.getExpire()<now){
                                        freeQueue.remove(i);
                                        p.destroy();
                                        System.out.println("删除"+p.id);
                                        i--;
                                        if(!canGc()){
                                            break;
                                        }
                                    }
                                }
                            }
                        }else {
                            condition.await();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                }
            }
        });
        thread.setDaemon(true);
        thread.setName("PoolImpl-清理超时的Connection");
        thread.start();
    }

    private void init(){
        try {
            Driver driver = (Driver) Class.forName(jdbcProperties.getDriverClass()).newInstance();
            DriverManager.registerDriver(driver);
            selectOne = new SelectOne(jdbcProperties.getWaitTimeout());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //唯一一处构造新连接
    private void connectMysql() throws InterruptedException {
        try {
            Connection conn = DriverManager.getConnection(jdbcProperties.getUrl(), jdbcProperties.getUser(), jdbcProperties.getPassword());
            PigeonConnection pc = new PigeonConnection(conn);
            pc.id = UUID.randomUUID().toString();
            freeQueue.add(pc);
            condition.signalAll();
            selectOne.putQueue(pc);
        }catch (Exception e){
            e.printStackTrace();
            TimeUnit.SECONDS.sleep(10);
            connectMysql();
        }
    }

    @Override
    public DbConnection getConnection() {
        PigeonConnection pg = get();
        DbConnection dbCon = new DbConnection(pg);
//        System.out.println(pg.id);
        return dbCon;
    }

    //阻塞拿链接 这样同一时间只能有一个线程拿到connection 每个connection 都是一个独立的实例，事务相关互不干涉
    private PigeonConnection get(){
        try {
            lock.lock();
            while (freeQueue.size()<=0){
                if(freeQueue.size()+busingQueue.size()>=jdbcProperties.getMaximumPoolSize()){
                    condition.await();
                }else {
                    connectMysql();
                }
            }
            PigeonConnection pc = freeQueue.remove(0);
            busingQueue.add(pc);
            return pc;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return null;
    }




    public class PigeonConnection {

        private String id;
        private Connection connection;
        private ReentrantLock conLock = new ReentrantLock();

        private volatile long expire;

        public PigeonConnection(Connection connection) {
            this.connection = connection;
        }

        public long getExpire() {
            return expire;
        }

        public void release(){
            try {
                lock.lock();
                if(busingQueue.size()>0){
                    busingQueue.remove(this);
                    freeQueue.add(this);
                    expire = System.currentTimeMillis() + jdbcProperties.getKeepAliveTime();//重新计算过期时间
                }
                condition.signalAll();
            }finally {
                lock.unlock();
            }
        }

        private void destroy(){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                connection = null;
            }
        }

        public void reConnect(){
            clearPool();
        }



        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public Connection getConnection() {
            return connection;
        }

        public void setConnection(Connection connection) {
            this.connection = connection;
        }

        public void setExpire(long expire) {
            try {
                conLock.lock();
                this.expire = expire;
            }finally {
                conLock.unlock();
            }
        }
    }

    private void clearPool(){
        try {
            lock.lock();
            gcSelf(freeQueue);
            gcSelf(busingQueue);
        }finally {
            lock.unlock();
        }
    }
    private void gcSelf(List<PigeonConnection> queue){
        if(queue!=null && queue.size()>0)
            for(int i=0;i<queue.size();i++) {
                PigeonConnection connection = queue.remove(i);
                connection.destroy();
                i--;
            }
    }

}
